package log

import (
	"context"
	"fmt"
	"os"
	"runtime"
	"strings"
	"time"

	"golang.org/x/sync/errgroup"
)

func New(app string, opts ...Option) ILogger {
	cfg := defaultCfg
	cfg.app = app
	for _, o := range opts {
		o(cfg)
	}
	switch {
	case cfg.file.toFile:
		cfg.ew = append(cfg.ew, NewFileEncodeWriter(cfg))
	}
	return &Logger{cfg: cfg, encodeWriters: cfg.ew}
}

type Logger struct {
	cfg           *Config
	filter        string
	encodeWriters []EncodeWriter
}

func (log *Logger) DebugWithContext(ctx context.Context, a ...interface{}) {
	log.writeWithTraceID(GetTraceID(ctx), DebugLevel, a...)
}

func (log *Logger) DebugfWithContext(ctx context.Context, format string, a ...interface{}) {
	log.writefWithTraceID(GetTraceID(ctx), DebugLevel, format, a...)
}
func (log *Logger) InfoWithContext(ctx context.Context, a ...interface{}) {
	log.writeWithTraceID(GetTraceID(ctx), InfoLevel, a...)
}
func (log *Logger) InfofWithContext(ctx context.Context, format string, a ...interface{}) {
	log.writefWithTraceID(GetTraceID(ctx), InfoLevel, format, a...)
}
func (log *Logger) WarnWithContext(ctx context.Context, a ...interface{}) {
	log.writeWithTraceID(GetTraceID(ctx), WarnLevel, a...)
}
func (log *Logger) WarnfWithContext(ctx context.Context, format string, a ...interface{}) {
	log.writefWithTraceID(GetTraceID(ctx), WarnLevel, format, a...)
}
func (log *Logger) ErrorWithContext(ctx context.Context, a ...interface{}) {
	log.writeWithTraceID(GetTraceID(ctx), ErrorLevel, a...)
}
func (log *Logger) ErrorfWithContext(ctx context.Context, format string, a ...interface{}) {
	log.writefWithTraceID(GetTraceID(ctx), ErrorLevel, format, a...)
}
func (log *Logger) PanicWithContext(ctx context.Context, a ...interface{}) {
	log.writeWithTraceID(GetTraceID(ctx), PanicLevel, a...)
	panic(data(a...))
}
func (log *Logger) PanicfWithContext(ctx context.Context, format string, a ...interface{}) {
	log.writefWithTraceID(GetTraceID(ctx), PanicLevel, format, a...)
	panic(dataf(format, a...))
}

func (log *Logger) FatalWithContext(ctx context.Context, a ...interface{}) {
	log.writeWithTraceID(GetTraceID(ctx), FatalLevel, a...)
	os.Exit(-1)
}

func (log *Logger) FatalfWithContext(ctx context.Context, format string, a ...interface{}) {
	log.writefWithTraceID(GetTraceID(ctx), FatalLevel, format, a...)
	os.Exit(-1)
}

func (log *Logger) DebugWithTraceID(traceID string, a ...interface{}) {
	log.writeWithTraceID(traceID, DebugLevel, a...)
}

func (log *Logger) DebugfWithTraceID(traceID string, format string, a ...interface{}) {
	log.writefWithTraceID(traceID, DebugLevel, format, a...)
}

func (log *Logger) InfoWithTraceID(traceID string, a ...interface{}) {
	log.writeWithTraceID(traceID, InfoLevel, a...)
}

func (log *Logger) InfofWithTraceID(traceID string, format string, a ...interface{}) {
	log.writefWithTraceID(traceID, InfoLevel, format, a...)
}

func (log *Logger) WarnWithTraceID(traceID string, a ...interface{}) {
	log.writeWithTraceID(traceID, WarnLevel, a...)
}

func (log *Logger) WarnfWithTraceID(traceID string, format string, a ...interface{}) {
	log.writefWithTraceID(traceID, WarnLevel, format, a...)
}

func (log *Logger) ErrorWithTraceID(traceID string, a ...interface{}) {
	log.writeWithTraceID(traceID, ErrorLevel, a...)
}

func (log *Logger) ErrorfWithTraceID(traceID string, format string, a ...interface{}) {
	log.writefWithTraceID(traceID, ErrorLevel, format, a...)
}

func (log *Logger) PanicWithTraceID(traceID string, a ...interface{}) {
	log.writeWithTraceID(traceID, PanicLevel, a...)
	panic(data(a...))
}

func (log *Logger) PanicfWithTraceID(traceID string, format string, a ...interface{}) {
	log.writefWithTraceID(traceID, PanicLevel, format, a...)
	panic(dataf(format, a...))
}

func (log *Logger) FatalWithTraceID(traceID string, a ...interface{}) {
	log.writeWithTraceID(traceID, FatalLevel, a...)
}

func (log *Logger) FatalfWithTraceID(traceID string, format string, a ...interface{}) {
	log.writefWithTraceID(traceID, FatalLevel, format, a...)
}

func (log *Logger) Debug(a ...interface{}) {
	log.writeWithTraceID("", DebugLevel, a...)
}

func (log *Logger) Debugf(format string, a ...interface{}) {
	log.writefWithTraceID("", DebugLevel, format, a...)
}

func (log *Logger) Info(a ...interface{}) {
	log.writeWithTraceID("", InfoLevel, a...)
}

func (log *Logger) Infof(format string, a ...interface{}) {
	log.writefWithTraceID("", InfoLevel, format, a...)
}

func (log *Logger) Warn(a ...interface{}) {
	log.writeWithTraceID("", WarnLevel, a...)
}

func (log *Logger) Warnf(format string, a ...interface{}) {
	log.writefWithTraceID("", WarnLevel, format, a...)
}

func (log *Logger) Error(a ...interface{}) {
	log.writeWithTraceID("", ErrorLevel, a...)
}

func (log *Logger) Errorf(format string, a ...interface{}) {
	log.writefWithTraceID("", ErrorLevel, format, a...)
}

func (log *Logger) Panic(a ...interface{}) {
	log.writeWithTraceID("", PanicLevel, a...)
	panic(data(a...))
}

func (log *Logger) Panicf(format string, a ...interface{}) {
	log.writefWithTraceID("", PanicLevel, format, a...)
	panic(dataf(format, a...))
}

func (log *Logger) Fatal(a ...interface{}) {
	log.writeWithTraceID("", FatalLevel, a...)
	os.Exit(-1)
}

func (log *Logger) Fatalf(format string, a ...interface{}) {
	log.writefWithTraceID("", FatalLevel, format, a...)
	os.Exit(-1)
}

func (log *Logger) Filter(filter string) {
	log.filter = filter
}

func (log *Logger) writeWithTraceID(traceID string, l Level, a ...interface{}) {
	log.write(l, traceID, data(a...))
}

func (log *Logger) writefWithTraceID(traceID string, l Level, format string, a ...interface{}) {
	log.write(l, traceID, dataf(format, a...))
}

func (log *Logger) write(l Level, traceID, message string) {
	if l < log.cfg.level {
		return
	}
	if log.filter != "" && !strings.Contains(message, log.filter) {
		return
	}
	eg := errgroup.Group{}
	caller := ""
	if log.cfg.withCaller {
		caller = getCaller()
	}
	ent := Entry{
		LoggerName: log.cfg.app,
		Level:      l,
		Time:       time.Now(),
		TraceID:    traceID,
		Message:    message,
		Caller:     caller,
		Stack:      "",
	}

	if log.cfg.withStack && l >= ErrorLevel {
		ent.Stack = getStack()
	}

	//错误备注
	// if l >= ErrorLevel {
	// 	ent.Stack = getStack()
	// }

	for _, w := range log.encodeWriters {
		ew := w
		eg.Go(func() error {
			buf, err := ew.Encoder(ent)
			if err != nil {
				return err
			}
			sum := len(buf.Bytes())
			write := 0
			for write != sum {
				l, errW := ew.Write(buf.Bytes())
				if errW != nil {
					return err
				}
				write += l
			}
			return nil
		})
	}

	if err := eg.Wait(); err != nil {
		fmt.Println(ColorSprintf(Red, "write log error:%s", err.Error()))
		return
	}
	return
}

func getCaller() string {
	//todo 不同用法，层数可能不同
	if _, fileName, line, ok := runtime.Caller(5); ok {
		return fmt.Sprintf("%s:%d", fileName, line)
	}
	return ""
}

func data(a ...interface{}) string {
	return fmt.Sprint(a...)
}

func dataf(format string, a ...interface{}) string {
	return fmt.Sprintf(format, a...)
}
